module VirtualCity.City

open Casanova
open Casanova.Utilities
open Casanova.Core
open Casanova.Coroutines
open Casanova.Game
open Casanova.Input
open Casanova.Math
open Casanova.Drawing
open VirtualCity.Rendering
open Casanova.StandardLibrary.Core
open Casanova.StandardLibrary.Physics
open VirtualCity
open VirtualCity.CityDescription
open VirtualCity.PlanDescription
open System


/// The simulated city contains a grid of roads and buildings.
/// Buses connect the various buildings, so that agents may
/// (at a small monetary cost) travel faster.
/// Buildings contain items, which allow an agent to perform
/// actions such as sleeping, eating, working, etc. 
/// The city maintains a clock that determines the current
/// time.
type [<CasanovaEntity>] City = { 
  Agents        : Var<List<Agent>>
  BlocksPerSide : int
  BlockSize     : float32<m>
  Buildings     : List<Building>
  Buses     : List<Bus>
  Roads         : List<Road>
  Clock         : Clock
  WalkGraph     : WalkGraph
}
    

/// A building is a visual and physical entity that acts
/// as a container for a set of items. It also stores a
/// description, which holds no more than the building 
/// position.
and [<CasanovaEntity>] Building = { 
  Base              : BuildingDescription
  Visual            : VisualEntity
  Caption           : ShadowedText
//  ItemsCaption      : DrawableText
  //Physical          : PhysicalEntity
  Items             : Var<List<Item>>
} with
  /// Get building position
  member self.Position = self.Base.Position
  static member ItemsCaptionStringRule(self:Building,dt:float32<s>) =
    let items = !self.Items |> Seq.groupBy (fun i -> i.Name)
    [
      for name,items in items do
        if items |> Seq.length = 1 then
          yield "*" + name + "\n"
        else
          yield sprintf "* %sx%d\n" name (items |> Seq.length)
      for i = items |> Seq.length to 3 do
        yield "*\n"
    ] |> Seq.fold (+) ""


/// A bus is a visual and physical entity that circles around 
/// the city and that stops at various predefined bus stops
/// to load and unload passengers.
/// Every bus has a different ticket price.
and [<CasanovaEntity>] Bus = {
  Visual      : VisualEntity
  Physics     : PhysicalEntity
  Base        : BusDescription
  Stops       : List<BusStop>
  Passengers  : Var<List<Passenger>>
} with  
  static member VisualPositionRule(self:Bus,dt:float32<s>) = !self.Physics.Position
  member this.Number = this.Base.Number
  member this.TicketPrice = this.Base.TicketPrice


/// The set of passengers of a bus are stored with their destination.
and [<CasanovaEntity>] Passenger = {
  Agent       : Ref<Agent>
  Destination : Ref<BusStop>
}


/// A bus stop is a visual entity that stores a list of agents waiting for
/// a specified bus. Multiple stops may exist at the same location for
/// different buses. Agents waiting for a bus are set in the "BusWaiters"
/// list.
and [<CasanovaEntity>] BusStop = { 
  Base           : BusStopDescription
  Visual         : VisualEntity
  BusWaiters     : Var<List<BusWaiter>>
} with
  member this.Name = this.Base.Name
  member this.BusNumber = this.Base.BusNumber
  member this.Location = this.Base.Location
  member this.Time = this.Base.Time


/// An agent waiting for a bus and going to a certain bus stop.
and [<CasanovaEntity>] BusWaiter = {
  Agent          : Ref<Agent>
  BusNumber      : int
  Destination    : Ref<BusStop>
}


/// A road is a visual entity that connects two end-points.
and [<CasanovaEntity>] Road = { 
  Visual   : DrawableLine
  Base     : RoadDescription
} with
  member this.Name = this.Base.Name
  member this.Start = this.Base.Start
  member this.End = this.Base.End


/// An item is a visual and physical entity in the virtual world. 
/// Items may be used, and using them produces certain effects 
/// described in "UseEffects". Items may only be used at specified
/// times, described in "UseSlots".
and [<CasanovaEntity>] Item = { 
  Visual          : Option<VisualEntity>
  Physical        : Option<PhysicalEntity>
  Base            : ItemDescription
  Location        : Vector2<m>
} with
  member this.Name = this.Base.Name
  member this.UseEffects = this.Base.UseEffects
  member this.UseSlots = this.Base.UseSlots
  /// Implicitly convert an item to its description
  static member op_Implicit(r:Item) = r.Base


/// The use slot for the city is the same as the description of the use slot
/// in the city description.
and UseSlot = CityDescription.UseSlot


/// The stats for an agent in the city are the same as the description of the stats
/// in the city description.
and Stats<[<Measure>] 'u> = CityDescription.Stats<'u>


/// An agent is a visual and physical entity. An agent has a 
/// home and a work building assigned where he may use the 
/// local items. An agent also has a certain set of stats, and 
/// a certain amount of money that are affected by performing
/// actions such as moving around or using items. 
/// An agent becomes half transparent when his stats are low,
/// and he becomes red when one or more of its stats reaches zero.
/// The current sequence of actions that the agent is performing
/// are described in his "ActivePlan", and the current action
/// that is being performed is described in his "CurrentAction".
/// An agent tracks whether or not it is currently selected, in
/// order to provide debug prints for the current agent.
/// Agents have a StatsIdleMask that defines which stats are
/// decreased just for idling. This allows to reduce (or increase)
/// the cost of idling.
and [<CasanovaEntity>] Agent = { 
  Name          : string
  Visual        : VisualEntity
  VisualOffset  : Vector2<m>
  Physical      : PhysicalEntity
  Home          : Ref<Building>
  Work          : Ref<Building>
  Stats         : Rule<Stats<1>>
  Money         : Var<Money>
  ActivePlan    : Var<PlanDescription<ActionDescription>>
  CurrentAction : Var<string>
  DebugLog      : Var<string>
  Selected      : Var<bool>
  StatsIdleMask : Var<Stats<1>>
} with
  member agent.Inventory = !(!agent.Home).Items
  static member VisualPositionRule(self:Agent,dt:float32<s>) = (!self.Physical.Position + self.VisualOffset) * 1.0f<pixel/m>
  static member VisualColorRule(self:Agent,dt:float32<s>) = 
    if (!self.Stats).Rested <= 0.0f && (!self.Stats).Sated <= 0.0f then
      Color.Red
    else
      Color.White * ((!self.Stats).Min * 0.25f + 0.75f)
  member self.IdleStatsLoss = VirtualCity.CityDescription.AgentDescription.IdleStatsLossBase
  static member StatsRule(self:Agent,dt:float32<s>) =
    !self.Stats - Stats.Multiply(self.IdleStatsLoss * dt, !self.StatsIdleMask)
